import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.net.*;
import java.io.*;
import java.util.*;

public class ChatClientRMI extends UnicastRemoteObject implements ClientRMI {
  public String name;
  private HashMap<String, ClientRMI> chatGroup;
  private ServerRMI server;

  //Thread which repeatedly sends a heartbeat from 'client' to 'server'.
  private class HeartbeatSender extends TimerTask {
    private ServerRMI server;
    private ClientRMI client;

    public HeartbeatSender(ServerRMI server, ClientRMI client){
      this.server = server;
      this.client = client;
    }

    // Send heartbeat to server
    public void run() {
      try {
        server.heartbeat(client);
      } catch(RemoteException e) {
        System.err.println(e.getMessage());
      }
    }
  }

  //Constructs a dummy RMI chat client
  public ChatClientRMI() throws RemoteException {
    super();
  }
  
  //Constructs a human-interactive RMI chat client
  public ChatClientRMI(String serverHostName) throws RemoteException {
    // Prompt user for name
    Scanner stdin = new Scanner(System.in);
    System.out.println("Please input your name:");
    name = stdin.nextLine();

    //connect to the server
    try {
      String name = "ServerRMI";
      Registry registry = LocateRegistry.getRegistry(serverHostName);
      server = (ServerRMI) registry.lookup(name);
      server.registerClient(this);
      chatGroup = server.getGroup();
    } catch(Exception e) {
      System.err.println("Error: " + e.getMessage());
      System.exit(1);
    }

    //set up heartbeat sender
    TimerTask heartbeatSender = new HeartbeatSender(server, this); 
    Timer heartbeatTimer = new Timer();
    heartbeatTimer.schedule(heartbeatSender, 30*1000, 30*1000);

    // Read commands from stdin
    System.out.print("$ ");
    String line = null;
    while (true) {
      try{
        line = stdin.nextLine();
        String[] cmdParts = line.split(" ", 3);
        String cmd = cmdParts[0];
        // Quit the chat client
        if (cmd.equals("exit")) {
          server.unregisterClient(this);
          break;
        }
        if (cmd.equals("list")) {
          chatGroup = server.getGroup();
          for (String key : chatGroup.keySet()){
            ClientRMI client = chatGroup.get(key);
            if (client.getName().equals(this.name))
              System.out.println(client.getName() + "(me) ");
            else
              System.out.println(client.getName() + " ");
          }
        }
        if (cmd.equals("chat")) {
          if (cmdParts.length < 3) {
            System.out.println("Usage: chat user message");
          } else {
            //First make sure the group list is up to date
            chatGroup = server.getGroup();
            //Figure out who we're sending the message to
            String targetName = cmdParts[1];
            ClientRMI to = chatGroup.get(targetName);
            if(to == null){
              System.err.println("That user is not part of the chat room");
              chatGroup = server.getGroup();
            }else{
              //Send message
              to.message(this, cmdParts[2]);
            }
          }
        }
        System.out.print("$ ");
      } catch(RemoteException e) {
        System.err.println("Error: "+e.getMessage());
      }
    }
    
    heartbeatTimer.cancel();
  }

  public String getName() throws RemoteException{
    return name;
  }

  public void message(ClientRMI from, String msg) throws RemoteException{
    System.out.println(from.getName() + ": " + msg);
  }

  public static void main(String[] args) {
    if(args.length == 1){//create 1 human-interactive client
      if(System.getSecurityManager() == null) {
        System.setSecurityManager(new SecurityManager());
      }
      try{
        ChatClientRMI client = new ChatClientRMI(args[0]);
        UnicastRemoteObject.unexportObject(client, false);
      } catch(RemoteException e) {
        System.err.println(e.getMessage());
      }
    }else if(args.length == 2){
      if(args[0].equals("--dummy")){//run the dummy client test
        try {
          //get the remote server object stub
          String name = "ServerRMI";
          Registry registry = LocateRegistry.getRegistry(args[1]);
          ServerRMI dummyServer = (ServerRMI) registry.lookup(name);

          ChatClientRMI clients[] = new ChatClientRMI[100000];
          //get the local hostname to create a unique chat client user name
          String thishost = InetAddress.getLocalHost().getHostName();

          long start = System.currentTimeMillis();
          long latency_start = 0;
          //register all the dummy users
          for(int i = 0; i < 100000; i++){
            //set up client
            clients[i] = new ChatClientRMI();
            clients[i].name = thishost+i;

            //register client
            long latency_tmp = System.currentTimeMillis();
            dummyServer.registerClient(clients[i]);
            latency_start += System.currentTimeMillis() - latency_tmp;
          }

          //calculate and print statistics
          double throughput = (100000*1000) / (double)(System.currentTimeMillis() - start);
          double latency = (double)latency_start / (100000*1000);
          System.out.println("Throughput: " + throughput);
          System.out.println("Latency: " + latency);

          //remove clients from RMI framework
          for(int i = 0; i < 100000; i++){
            UnicastRemoteObject.unexportObject(clients[i], false);
          }

        } catch(Exception e) {
          System.err.println("Error: " + e.getMessage());
          System.exit(1);
        }
      }else{
        System.err.println("Invalid option '"+args[0]+"'");
        System.exit(1);
      }
    }else{//unsupported number of args
      System.err.println("Usage: ChatClientRMI [--dummy] <server_hostname>");
      System.exit(1);
    }
  }
}
